#region Using directives.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
#endregion
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Displays a collection of <see cref="ICommand"/> objects in a <see cref="Menu"/> control.
    /// </summary>
    [ToolboxData("<{0}:CommandMenu runat=server></{0}:CommandMenu>")]
    [Designer(typeof(CommandMenuDesigner))]
    public class CommandMenu : WebControl, INamingContainer, ICommandDisplay
    {        
        #region MenuType enum.

        /// <summary>
        /// Specifies how a <b>CommandMenu</b> control is rendered.
        /// </summary>
        public enum CommandMenuType
        {
            /// <summary>
            /// Specifies that the menu behaves like a static Windows MenuStrip which is always displayed.
            /// </summary>
            MenuStrip,

            /// <summary>
            /// Specifies that the menu behaves like a Windows context-menu that may change the position.
            /// </summary>
            /// <remarks>Initially hidden, triggered by client-side event. <see cref="CommandMenu.ShowMenuJScript"/> 
            /// getter returns a JavaScript code which displays the menu.</remarks>
            ContextMenu            
        }

        #endregion

        #region Fields.

        CommandMenuType menuType;
        CommandGroupStyle commandHierarchy = new CommandGroupStyle();
        IList<ICommand> commands = new List<ICommand>();
        IActor actor;
        IMessageFormatter formatter;
        bool displayReasonForDisabledItems = true;
        bool horizontalSecurityEnabled = true;
        bool ajaxEnabled;
        bool asyncPostBackOnShow;        

        #endregion

        #region Child controls.

        Menu contextMenu = new Menu();

        // Three textboxes are a part of update panel. The button invokes async postback.
        UpdatePanel atlasPanel = new UpdatePanel();
        TextBox txtDisabledItems = new TextBox();
        TextBox txtDisabledToolTips = new TextBox();
        TextBox txtHiddenItems = new TextBox();
        Button btnAsyncCheckCommands = new Button();       

        #endregion

        #region CTor and CreateChildControls.

        /// <summary>
        /// Initializes a new instance of the <b>CommandMenu</b> class.
        /// </summary>
        public CommandMenu()
        {
            // If true, the menu items would be duplicated even though "this.contextMenu.Items.Clear();"
            // is invoked before the command items are created.
            this.contextMenu.EnableViewState = false;

            this.txtDisabledItems.Style["display"] = "none";
            this.txtDisabledToolTips.Style["display"] = "none";
            this.txtHiddenItems.Style["display"] = "none";
            this.btnAsyncCheckCommands.Style["display"] = "none";

            this.atlasPanel.ChildrenAsTriggers = true;
            this.atlasPanel.UpdateMode = UpdatePanelUpdateMode.Always;
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based 
        /// implementation to create any child controls they contain in preparation for posting back or rendering. 
        /// </summary>
        protected override void CreateChildControls()
        {
            this.Controls.Clear();
            this.Controls.Add(this.contextMenu);

            this.ajaxEnabled = (ScriptManager.GetCurrent(this.Page) != null);
            if (this.AjaxEnabled)
            {
                this.Controls.Add(this.atlasPanel);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtDisabledItems);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtDisabledToolTips);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.txtHiddenItems);
                this.atlasPanel.ContentTemplateContainer.Controls.Add(this.btnAsyncCheckCommands);
            }
            else
            {
                this.Controls.Add(this.txtDisabledItems);
                this.Controls.Add(this.txtDisabledToolTips);
                this.Controls.Add(this.txtHiddenItems);
                this.Controls.Add(this.btnAsyncCheckCommands);
            }
        }

        #endregion

        #region OnInit. RegisterClientScriptResource.

        /// <summary>
        /// Raises the Init event. 
        /// </summary>
        /// <param name="e">An EventArgs object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            Page.ClientScript.RegisterClientScriptResource(this.GetType(), "BizElements.Web.CommandMenu.js");
        }

        #endregion

        #region Command collection methods.

        private static int IndexOfCommand(IList<ICommand> list, string key)
        {
            for (int cmdIdx = 0; cmdIdx < list.Count; cmdIdx++)
            {
                if (list[cmdIdx].Key == key)
                    return cmdIdx;
            }

            return -1;
        }

        #endregion

        #region Menu items: IDs, enable/disable.

        // Indices of commands which are available to actor, ie. which pass vertical security checks.
        List<int> availableCmdIndices = new List<int>();

        // Contains suffixes for client-side IDs of controls assigned to command objects.
        // Must be cleared whenever the items are (re)created.
        Dictionary<int, int> cmdIndicesAndIdSuffixes = new Dictionary<int, int>();

        // First item's TR element ID's ends with "0".
        const int firstItemTrElementIdSuffix = 0;

        // Must be reset to "0" whenever the items are (re)created.
        int nextCommandItemClientIdSuffix = firstItemTrElementIdSuffix;

        /// <summary>
        /// Gets the <b>ClientID</b> of the HTML (<b>tr</b>) element which represents the specified <b>ICommand</b> object on the UI.
        /// </summary>
        /// <param name="commandKey"><see cref="ICommand.Key"/> of the <see cref="ICommand"/> object contained in the <see cref="Commands"/> collection.</param>
        /// <returns>ID of the <b>tr</b> element or <b>null</b> if the command with the specified <b>key</b> is not found or rendered.</returns>
        /// <remarks>Do not invoke this method before the <see cref="Commands"/>, <see cref="CommandHierarchy"/> 
        /// and <see cref="Actor"/> properties have been set. The values of these properties cannot be changed 
        /// after the method has been invoked since it triggers the creation of child controls.</remarks>
        private string GetCommandClientID(string commandKey)
        {            
            int cmdIdx = IndexOfCommand(this.Commands, commandKey);
            if (cmdIdx < 0)
                return null;

            EnsureMenuItems();
            bool cmdMenuItemCreated = this.cmdIndicesAndIdSuffixes.ContainsKey(cmdIdx);
            if (!cmdMenuItemCreated)
                return null;

            int idSuffix = this.cmdIndicesAndIdSuffixes[cmdIdx];
            string trClientId = this.contextMenu.ClientID + "n" + idSuffix.ToString();
            return trClientId;
        }

        private void ResetDisabledItems()
        {
            // ';' indicates to client-side script that async postback has completed but no items should be disabled.
            this.txtDisabledItems.Text = ";";
            this.txtDisabledToolTips.Text = ";";
        }

        private void ResetHiddenItems()
        {
            // ';' indicates to client-side script that async postback has completed but no items should be hidden.
            this.txtHiddenItems.Text = ";";
        }

        private void DisableCommand(string commandKey, string tooltip)
        {
            string itemId = GetCommandClientID(commandKey);
            if (itemId != null)
            {
                this.txtDisabledItems.Text += itemId + ";";
                tooltip = (tooltip != null) ? tooltip.Replace(';', ',') : "";
                this.txtDisabledToolTips.Text += tooltip + ";";
            }
        }

        private void HideCommand(string commandKey)
        {
            string itemId = GetCommandClientID(commandKey);
            if (itemId != null)
                this.txtHiddenItems.Text += itemId + ";";
        }

        #endregion

        #region CreateMenuItems using level-order (breath-first) tree traversal.

        // ASP.NET Menu uses level-order tree traversal algorithm when rendering items (breath-first traversal).
        // The same order is used to assign client-side IDs to TR elements that represent menu items.
        // WARNING: Discovered by reverse engineering. Future ASP.NET releases may change the algorithm.
        // Currently, this is the only possible mechanism to determine client-side IDs for menu items.

        // In graph theory, breadth-first search (BFS) is a graph search algorithm that begins at the root node 
        // and explores all the neighboring nodes. Then for each of those nearest nodes, it explores their 
        // unexplored neighbour nodes, and so on, until it finds the goal.

        // First, all items of the current level are rendered. Then all items of the current level are inspected 
        // to see if they contain children. If submenu exists than it is recursively rendered (all it's levels). 
        // After the submenu is rendered, the recursion returns to current level to inspect other 1st level items.

        // ITEM         ID
        //
        // 1            0
        // 2            1
        //   2.1        5
        //     2.1.1    8
        //     2.1.2    9
        //   2.2        6
        //   2.3        7
        // 3            2
        // 4            3
        //   4.1        10
        //   4.2        11
        //     4.2.1    13
        //   4.3        12
        // 5            4      

        bool menuItemsCreated;

        private void EnsureMenuItems()
        {
            if (!this.menuItemsCreated)
                CreateMenuItems();
        }

        private void DisallowPropertyChangeIfMenuItemsCreated(string propertyWhichCannotBeChangedAfterMenuItemsHaveBeenCreated)
        {
            // The following properties must not be changed after the menu items have been created: Commands, CommandHierarchy, Actor.
            if (this.menuItemsCreated)
                throw new InvalidOperationException("Cannot change '" + propertyWhichCannotBeChangedAfterMenuItemsHaveBeenCreated + "' property after the menu items have been created.");
        }

        private void CreateMenuItems()
        {
            if (!this.DesignMode)
            {
                if (!string.IsNullOrEmpty(this.CommandHierarchySrc))
                {
                    string fileSystemPath = Page.Server.MapPath(this.CommandHierarchySrc);
                    this.CommandHierarchy.ReadXmlFile(fileSystemPath, CultureInfo.CurrentUICulture);
                }
            }

            bool autoCreateCommandItems = (this.AutoGenerateCommandHierarchy && this.CommandHierarchy.Items.Count == 0);
            if (autoCreateCommandItems)
                this.CommandHierarchy = new CommandGroupStyle(this.Commands, this.MinParameterSetSize, this.MaxParameterSetSize);

            this.nextCommandItemClientIdSuffix = firstItemTrElementIdSuffix;
            this.cmdIndicesAndIdSuffixes.Clear();

            this.CommandHierarchy.ExtendedProperties.WriteToObject(this.contextMenu, true);
            this.contextMenu.ToolTip = (this.CommandHierarchy.Description != null) ? this.CommandHierarchy.Description : this.CommandHierarchy.Text;
            this.contextMenu.Items.Clear();
            CreateSubMenuItems(this.contextMenu.Items, this.CommandHierarchy);

            this.menuItemsCreated = true;
        }

        private void CreateSubMenuItems(MenuItemCollection parentMenu, CommandGroupStyle group)
        {
            List<Pair<CommandGroupStyle, MenuItemCollection>> subGroupsAndEmptyMenuItemCollections = new List<Pair<CommandGroupStyle, MenuItemCollection>>();
            for (int itemIdx = 0; itemIdx < group.Items.Count; itemIdx++)
            {
                CommandGroupItem child = group.Items[itemIdx];
                if (child.Group != null)
                {
                    MenuItemCollection emptySubMenu = AddEmptySubMenu(parentMenu, child.Group);
                    subGroupsAndEmptyMenuItemCollections.Add(new Pair<CommandGroupStyle, MenuItemCollection>(child.Group, emptySubMenu));
                }
                else if (child.Command != null)
                {
                    AddCommand(parentMenu, child.Command);
                }
            }

            foreach (Pair<CommandGroupStyle, MenuItemCollection> submenu in subGroupsAndEmptyMenuItemCollections)
                CreateSubMenuItems(submenu.Second, submenu.First);
        }

        private MenuItemCollection AddEmptySubMenu(MenuItemCollection parent, CommandGroupStyle group)
        {
            MenuItem submenu = new MenuItem();
            parent.Add(submenu);
            group.ExtendedProperties.WriteToObject(submenu, true);
            submenu.Text = group.Text;
            submenu.ToolTip = (group.Description != null) ? group.Description : group.Text;
            submenu.Selectable = false;

            this.nextCommandItemClientIdSuffix++;
            return submenu.ChildItems;
        }        

        private void AddCommand(MenuItemCollection container, CommandStyle style)
        {
            int cmdIdx = IndexOfCommand(this.commands, style.Key);
            IWebCommand cmd = (cmdIdx >= 0) ? this.Commands[cmdIdx] as IWebCommand : null;
            if (cmd == null)
                return;

            if (!ShouldRenderCommand(cmd, style))
                return;

            MenuItem item = new MenuItem();
            container.Add(item);
            style.ExtendedProperties.WriteToObject(item, true);
            item.Text = (style.Text != null) ? style.Text : cmd.Text;
            item.ToolTip = (style.Description != null) ? style.Description : item.Text;
            item.NavigateUrl = "javascript:" + cmd.ExecuteOnClient;
            bool notAvailable = ShouldDisableCommand(cmd, style);
            if (notAvailable)
                item.Enabled = false;
            else
                this.availableCmdIndices.Add(cmdIdx);

            // Pair command object (its index in the collection) with client-side TR element (its ID suffix).
            int currItemClientIdSuffix = this.nextCommandItemClientIdSuffix;
            this.cmdIndicesAndIdSuffixes.Add(cmdIdx, currItemClientIdSuffix);
            this.nextCommandItemClientIdSuffix++;
        }

        private bool ShouldRenderCommand(ICommand cmd, CommandStyle style)
        {
            bool obeysMinSize = (this.MinParameterSetSize >= 0) ? cmd.MinParameterSetSize >= this.MinParameterSetSize : true;
            bool obeysMaxSize = (this.MaxParameterSetSize >= 0) ? cmd.MaxParameterSetSize <= this.MaxParameterSetSize : true;
            if (!obeysMinSize || !obeysMaxSize)
                return false;

            bool invisibleIfUnavailable = (style.UnavailableStyle == CommandStyle.DisableStyle.Automatic || style.UnavailableStyle == CommandStyle.DisableStyle.Hide);
            if (invisibleIfUnavailable)
            {
                bool isUnavailableAndHidden = (this.Actor != null && cmd.IsAvailableToActor(this.Actor).HasBrokenRules);
                if (isUnavailableAndHidden)
                    return false;
            }

            return true;
        }

        private bool ShouldDisableCommand(ICommand cmd, CommandStyle style)
        {
            bool isUnavailableButVisible = (this.Actor != null && cmd.IsAvailableToActor(this.Actor).HasBrokenRules);
            bool disable = isUnavailableButVisible && (style.UnavailableStyle == CommandStyle.DisableStyle.Disable);
            return disable;
        }

        #endregion

        #region Horizontal security.

        private void EnforceHorizontalSecurity()
        {
            ResetDisabledItems();
            ResetHiddenItems();
            if (!this.HorizontalSecurityEnabled || this.Actor == null)
                return;

            foreach (int cmdIdx in this.availableCmdIndices)
            {
                ICommand cmd = this.Commands[cmdIdx];

                CommandStyle style = this.CommandHierarchy.FindCommand(cmd.Key);
                bool dontEnforceSecurityForCurrentCommand = (style != null && style.NotGrantedStyle == CommandStyle.DisableStyle.None);
                if (dontEnforceSecurityForCurrentCommand)
                    continue;

                bool doesntAcceptParams = (cmd.MaxParameterSetSize < 1);
                if (doesntAcceptParams)
                    continue;

                // PrepareForExecution sets parameters and checks if it's safe to execute the command.
                bool cannotInvokeGrantExecuteMethod = !cmd.PrepareForExecution();
                bool recordCountValid = CheckRecordCountForCommand(cmd);
                if (!recordCountValid)
                {
                    // Disable. No tooltip. Record count is checked even if PrepareForExecution failed for some other reason.
                    DisableOrHideCommand(cmd, "");
                }

                if (cannotInvokeGrantExecuteMethod)
                    continue;
                
                if (cmd.Parameters.Count > 0)
                {
                    // Check grants. Disable if not granted. Broken rules in tooltip.
                    BusinessRuleCollection failedPermissions = cmd.GrantExecute(this.Actor).GetBrokenRules();
                    if (failedPermissions.HasBrokenRules)
                        DisableOrHideCommand(cmd, failedPermissions.ToString(this.Formatter));
                }
            }
        }

        private static bool CheckRecordCountForCommand(ICommand cmd)
        {
            bool cmdHasMinCount = (cmd.MinParameterSetSize >= 0);
            bool minCountViolated = (cmdHasMinCount && cmd.Parameters.Count < cmd.MinParameterSetSize);
            if (minCountViolated)
                return false;

            bool cmdHasMaxCout = (cmd.MaxParameterSetSize >= 0);
            bool maxCountViolated = (cmdHasMaxCout && cmd.Parameters.Count > cmd.MaxParameterSetSize);
            if (maxCountViolated)
                return false;

            return true;
        }

        private void DisableOrHideCommand(ICommand cmd, string failedPermissions)
        {
            CommandStyle style = this.CommandHierarchy.FindCommand(cmd.Key);
            CommandStyle.DisableStyle disableMode = (style != null) ? style.NotGrantedStyle : CommandStyle.DisableStyle.Automatic;
            switch (disableMode)
            {
                case CommandStyle.DisableStyle.Automatic:
                case CommandStyle.DisableStyle.Disable:
                    string reasonWhyItemIsDisabled = (this.DisplayReasonForDisabledItems) ? failedPermissions : "";
                    DisableCommand(cmd.Key, reasonWhyItemIsDisabled);
                    break;

                case CommandStyle.DisableStyle.Hide:
                    HideCommand(cmd.Key);
                    break;

                default:
                    // Leave enabled. Other components will enforce security once the command is selected.
                    break;
            }
        }

        #endregion

        #region OnPreRender and Render - ensure children, menu items and security.

        const string monitorInIntervalsFunctionCall = "Biz_CommandMenu_MonitorInIntervals('{0}', {1}, '{2}', '{3}', '{4}'); ";

        /// <summary>
        /// Raises the PreRender event.
        /// </summary>
        /// <param name="e">An EventArgs object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            EnsureChildControls();
            EnsureMenuItems();
            EnforceHorizontalSecurity();

            // The contents of the textbox which contain the IDs of disabled items is constantly monitored in browser.
            bool requiresConstantMonitoring = (!this.AsyncPostBackOnShow  ||  this.MenuType != CommandMenuType.ContextMenu);
            if (requiresConstantMonitoring && this.HorizontalSecurityEnabled)
            {
                int totalItemCount = GetTotalItemCount(this.CommandHierarchy.Items);
                string monitorSecurity = string.Format(monitorInIntervalsFunctionCall, this.contextMenu.ClientID, totalItemCount, this.txtDisabledItems.ClientID, this.txtHiddenItems.ClientID, this.txtDisabledToolTips.ClientID);
                Page.ClientScript.RegisterStartupScript(this.GetType(), this.ClientID + "_Monitor", monitorSecurity, true);
            }
        }

        /// <summary>
        /// Sends server control content to a provided HtmlTextWriter object, which writes the content to be rendered on the client.
        /// </summary>
        /// <param name="writer">The HtmlTextWriter object that receives the server control content.</param>
        /// <remarks>Creates command controls and renders them to client.</remarks>
        protected override void Render(HtmlTextWriter writer)
        {
            if (this.MenuType == CommandMenuType.ContextMenu)
            {
                this.contextMenu.Style["visibility"] = "hidden";
                this.contextMenu.Style["position"] = "absolute";
            }            

            base.Render(writer);
        }        

        #endregion

        #region GetDesignTimeHtml.

        internal string GetDesignTimeHtml()
        {
            System.IO.StringWriter strWriter = new System.IO.StringWriter();
            HtmlTextWriter htmWriter = new HtmlTextWriter(strWriter);
            EnsureChildControls();
            RenderControl(htmWriter);
            string designTimeHtml = strWriter.GetStringBuilder().ToString();
            return designTimeHtml;
        }

        #endregion

        #region Settings.

        /// <summary>
        /// Gets <see cref="ICommandDisplay"/> settings.
        /// </summary>
        /// <returns>A collection of key-value property pairs guaranteed to work only with 
        /// the <see cref="ICommandDisplay"/> implementation which created it.</returns>
        public virtual IDictionary<string, object> GetSettings()
        {
            Dictionary<string, object> settings = new Dictionary<string, object>();

            AddNonDefaultVal<Color>(settings, "BackColor", this.BackColor);
            AddNonDefaultVal<Color>(settings, "BorderColor", this.BorderColor);
            AddNonDefaultVal<BorderStyle>(settings, "BorderStyle", this.BorderStyle);
            AddNonDefaultVal<Unit>(settings, "BorderWidth", this.BorderWidth);
            AddNonDefaultString(settings, "CssClass", this.CssClass);
            settings.Add("Font", this.Font);
            AddNonDefaultVal<Color>(settings, "ForeColor", this.ForeColor);
            AddNonDefaultVal<Unit>(settings, "Height", this.Height);
            AddNonDefaultString(settings, "ToolTip", this.ToolTip);
            AddNonDefaultVal<Unit>(settings, "Width", this.Width);

            AddNonDefaultPositiveInt(settings, "DisappearAfter", this.DisappearAfter);
            AddNonDefaultString(settings, "DynamicBottomSeparatorImageUrl", this.DynamicBottomSeparatorImageUrl);
            if (!this.DynamicEnableDefaultPopOutImage)
                settings.Add("DynamicEnableDefaultPopOutImage", this.DynamicEnableDefaultPopOutImage);
            AddNonDefaultPositiveInt(settings, "DynamicHorizontalOffset", this.DynamicHorizontalOffset);
            AddNonDefaultStyle(settings, "DynamicHoverStyle", this.DynamicHoverStyle);
            AddNonDefaultString(settings, "DynamicItemFormatString", this.DynamicItemFormatString);
            AddNonDefaultStyle(settings, "DynamicMenuItemStyle", this.DynamicMenuItemStyle);
            AddNonDefaultStyle(settings, "DynamicMenuStyle", this.DynamicMenuStyle);
            AddNonDefaultString(settings, "DynamicPopOutImageTextFormatString", this.DynamicPopOutImageTextFormatString);
            AddNonDefaultString(settings, "DynamicPopOutImageUrl", this.DynamicPopOutImageUrl);
            AddNonDefaultStyle(settings, "DynamicSelectedStyle", this.DynamicSelectedStyle);
            AddNonDefaultString(settings, "DynamicTopSeparatorImageUrl", this.DynamicTopSeparatorImageUrl);
            AddNonDefaultVal<int>(settings, "DynamicVerticalOffset", this.DynamicVerticalOffset);
            AddNonDefaultVal<bool>(settings, "ItemWrap", this.ItemWrap);
            AddNonDefaultVal<Orientation>(settings, "Orientation", this.Orientation);
            AddNonDefaultString(settings, "ScrollDownImageUrl", this.ScrollDownImageUrl);
            AddNonDefaultString(settings, "ScrollDownText", this.ScrollDownText);
            AddNonDefaultString(settings, "ScrollUpImageUrl", this.ScrollUpImageUrl);
            AddNonDefaultString(settings, "ScrollUpText", this.ScrollUpText);
            AddNonDefaultString(settings, "SkipLinkText", this.SkipLinkText);
            AddNonDefaultString(settings, "StaticBottomSeparatorImageUrl", this.StaticBottomSeparatorImageUrl);
            if (!this.StaticEnableDefaultPopOutImage)
                settings.Add("StaticEnableDefaultPopOutImage", this.StaticEnableDefaultPopOutImage);
            AddNonDefaultStyle(settings, "StaticHoverStyle", this.StaticHoverStyle);
            AddNonDefaultString(settings, "StaticItemFormatString", this.StaticItemFormatString);
            AddNonDefaultStyle(settings, "StaticMenuItemStyle", this.StaticMenuItemStyle);
            AddNonDefaultStyle(settings, "StaticMenuStyle", this.StaticMenuStyle);
            AddNonDefaultString(settings, "StaticPopOutImageTextFormatString", this.StaticPopOutImageTextFormatString);
            AddNonDefaultString(settings, "StaticPopOutImageUrl", this.StaticPopOutImageUrl);
            AddNonDefaultStyle(settings, "StaticSelectedStyle", this.StaticSelectedStyle);
            AddNonDefaultVal<Unit>(settings, "StaticSubMenuIndent", this.StaticSubMenuIndent);
            AddNonDefaultString(settings, "StaticTopSeparatorImageUrl", this.StaticTopSeparatorImageUrl);

            AddNonDefaultVal<CommandMenuType>(settings, "MenuType", this.MenuType);
            if (!this.AutoGenerateCommandHierarchy)
                settings.Add("AutoGenerateCommandHierarchy", this.AutoGenerateCommandHierarchy);
            AddPositiveInt(settings, "MinParameterSetSize", this.MinParameterSetSize);
            AddPositiveInt(settings, "MaxParameterSetSize", this.MaxParameterSetSize);
            AddNonDefaultString(settings, "CommandHierarchySrc", this.CommandHierarchySrc);
            settings.Add("Formatter", this.Formatter);
            settings.Add("DisplayReasonForDisabledItems", this.DisplayReasonForDisabledItems);
            settings.Add("HorizontalSecurityEnabled", this.HorizontalSecurityEnabled);
            settings.Add("AsyncPostBackOnShow", this.AsyncPostBackOnShow);

            return settings;
        }

        private static void AddNonDefaultVal<T>(IDictionary<string, object> settings, string key, T val)
        {
            bool isDefault = val.Equals(default(T));
            if (!isDefault)
                settings.Add(key, val);
        }

        private static void AddNonDefaultPositiveInt(IDictionary<string, object> settings, string key, int val)
        {
            if (val > 0)
                settings.Add(key, val);
        }

        private static void AddPositiveInt(IDictionary<string, object> settings, string key, int val)
        {
            if (val >= 0)
                settings.Add(key, val);
        }

        private static void AddNonDefaultString(IDictionary<string, object> settings, string key, string val)
        {
            if (!string.IsNullOrEmpty(val))
                settings.Add(key, val);
        }

        private static void AddNonDefaultStyle(IDictionary<string, object> settings, string key, Style val)
        {
            if (val != null && !val.IsEmpty)
                settings.Add(key, val);
        }

        /// <summary>
        /// Applies <see cref="ICommandDisplay"/> settings to the current instance.
        /// </summary>
        /// <param name="settings">Settings collection which was created by the same or compatibile
        /// <see cref="ICommandDisplay"/> implementation as the current instance.</param>
        public virtual void ApplySettings(IDictionary<string, object> settings)
        {
            foreach (KeyValuePair<string, object> namedValue in settings)
            {
                object val = namedValue.Value;
                if (val == null)
                    continue;

                PropertyInfo property = GetType().GetProperty(namedValue.Key);
                if (property != null)
                {
                    if (property.CanWrite)
                    {
                        if (property.PropertyType.IsAssignableFrom(val.GetType()))
                            property.SetValue(this, val, null);
                    }
                    else
                    {
                        object currPropertyValue = property.GetValue(this, null);
                        if (currPropertyValue is Style && val is Style)
                            (currPropertyValue as Style).CopyFrom(val as Style);
                        else if (currPropertyValue is FontInfo && val is FontInfo)
                            (currPropertyValue as FontInfo).CopyFrom(val as FontInfo);                        
                    }
                }
            }
        }

        #endregion

        #region Overriden WebControl properties mapped to Menu properties.

        /// <summary>
        /// Gets or sets the background color of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Background color of the Web server control.")]
        public override System.Drawing.Color BackColor
        {
            get
            {
                return this.contextMenu.BackColor;
            }
            set
            {
                this.contextMenu.BackColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the border color of the Web control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border color of the Web control.")]
        public override System.Drawing.Color BorderColor
        {
            get
            {
                return this.contextMenu.BorderColor;
            }
            set
            {
                this.contextMenu.BorderColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the border style of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border style of the Web server control.")]
        public override BorderStyle BorderStyle
        {
            get
            {
                return this.contextMenu.BorderStyle;
            }
            set
            {
                this.contextMenu.BorderStyle = value;
            }
        }

        /// <summary>
        /// Gets or sets the border width of the Web server control. 
        /// </summary>
        [Category("Appearance")]
        [Description("Border width of the Web server control.")]
        public override Unit BorderWidth
        {
            get
            {
                return this.contextMenu.BorderWidth;
            }
            set
            {
                this.contextMenu.BorderWidth = value;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client.
        /// </summary>
        [Category("Appearance")]
        [Description("CSS class rendered by the Web server control on the client.")]
        public override string CssClass
        {
            get
            {
                return this.contextMenu.CssClass;
            }
            set
            {
                this.contextMenu.CssClass = value;
            }
        }

        /// <summary>
        /// Gets the font properties associated with the Web server control.
        /// </summary>
        [Category("Appearance")]
        [Description("Font properties associated with the Web server control.")]
        public override FontInfo Font
        {
            get
            {
                return this.contextMenu.Font;
            }
        }

        /// <summary>
        /// Gets or sets the foreground color (typically the color of the text) of the Web server control.
        /// </summary>
        [Category("Appearance")]
        [Description("Foreground color (typically the color of the text) of the Web server control.")]
        public override System.Drawing.Color ForeColor
        {
            get
            {
                return this.contextMenu.ForeColor;
            }
            set
            {
                this.contextMenu.ForeColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the height of the Web server control.
        /// </summary>
        [Category("Layout")]
        [Description("Height of the Web server control.")]
        public override Unit Height
        {
            get
            {
                return this.contextMenu.Height;
            }
            set
            {
                this.contextMenu.Height = value;
            }
        }

        /// <summary>
        /// Gets or sets the width of the Web server control.
        /// </summary>
        [Category("Layout")]
        [Description("Width of the Web server control.")]
        public override Unit Width
        {
            get
            {
                return this.contextMenu.Width;
            }
            set
            {
                this.contextMenu.Width = value;
            }
        }

        /// <summary>
        /// Gets or sets the text displayed when the mouse pointer hovers over the Web server control.
        /// </summary>
        [Category("Behavior")]
        [Description("Text displayed when the mouse pointer hovers over the Web server control.")]
        public override string ToolTip
        {
            get
            {
                return this.contextMenu.ToolTip;
            }
            set
            {
                this.contextMenu.ToolTip = value;
            }
        }

        #endregion

        #region Menu properties.

        /// <summary>
        /// Gets or sets the duration for which a dynamic menu is displayed after the mouse pointer is no longer positioned over the menu. 
        /// </summary>
        /// <value>The amount of time (in milliseconds) a dynamic menu is displayed after the mouse pointer is no longer positioned over the menu. The default is 500.</value>
        [ThemeableAttribute(false)]
        [Category("Behavior")]
        [DefaultValue(500)]
        [Description("Amount of time (in milliseconds) a dynamic menu is displayed after the mouse pointer is no longer positioned over the menu.")]
        public virtual int DisappearAfter
        {
            get { return this.contextMenu.DisappearAfter; }
            set { this.contextMenu.DisappearAfter = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image to display at the bottom of each dynamic menu item to separate it from other menu items. 
        /// </summary>
        /// <value>URL to a separator image displayed at the bottom of each dynamic menu item. The default value is an empty string (""), which indicates that this property is not set.</value>
        [ThemeableAttribute(true)]
        [Category("Appearance")]
        [Description("The URL to a separator image displayed at the bottom of each dynamic menu item.")]
        [DefaultValue("")]
        public virtual string DynamicBottomSeparatorImageUrl
        {
            get { return this.contextMenu.DynamicBottomSeparatorImageUrl; }
            set { this.contextMenu.DynamicBottomSeparatorImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the built-in image that indicates that a dynamic menu item has a submenu is displayed. 
        /// </summary>
        /// <value>true to display the built-in image for dynamic menu items with submenus; otherwise, false. The default is true.</value>
        [Category("Appearance")]
        [DefaultValue(true)]
        [Description("Whether the built-in image that indicates that a dynamic menu item has a submenu is displayed.")]
        public virtual bool DynamicEnableDefaultPopOutImage
        {
            get { return this.contextMenu.DynamicEnableDefaultPopOutImage; }
            set { this.contextMenu.DynamicEnableDefaultPopOutImage = value; }
        }

        /// <summary>
        /// Gets or sets the number of pixels to shift a dynamic menu horizontally relative to its parent menu item.
        /// </summary>
        /// <value>The number of pixels to shift a dynamic menu horizontally relative to its parent menu item. The default is 0.</value>
        [Category("Appearance")]
        [DefaultValue(0)]
        [Description("Number of pixels to shift a dynamic menu horizontally relative to its parent menu item.")]
        public virtual int DynamicHorizontalOffset
        {
            get { return this.contextMenu.DynamicHorizontalOffset; }
            set { this.contextMenu.DynamicHorizontalOffset = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of a dynamic menu item when the mouse pointer is positioned over it.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of a dynamic menu item when the mouse pointer is positioned over it.</value>
        [Category("Style")]
        [Description("Style of a dynamic menu item when the mouse pointer is positioned over it.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual Style DynamicHoverStyle
        {
            get { return this.contextMenu.DynamicHoverStyle; }
        }

        /// <summary>
        /// Gets or sets additional text shown with all menu items that are dynamically displayed. 
        /// </summary>
        /// <value>The additional text or characters that appear with all menu items.</value>
        [Category("Appearance")]
        [Description("Additional text or characters that appear with all menu items.")]
        [DefaultValue("")]
        public virtual string DynamicItemFormatString
        {
            get { return this.contextMenu.DynamicItemFormatString; }
            set { this.contextMenu.DynamicItemFormatString = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of the menu items within a dynamic menu.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of the menu items within a dynamic menu.</value>
        [Category("Style")]
        [Description("Style of the menu items within a dynamic menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual MenuItemStyle DynamicMenuItemStyle
        {
            get { return this.contextMenu.DynamicMenuItemStyle; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of a dynamic menu. 
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of a dynamic menu.</value>
        [Category("Style")]
        [Description("Style of a dynamic menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual SubMenuStyle DynamicMenuStyle
        {
            get { return this.contextMenu.DynamicMenuStyle; }
        }

        /// <summary>
        /// Gets or sets the alternate text for the image used to indicate that a dynamic menu item has a submenu.
        /// </summary>
        /// <value>The alternate text for the image used to indicate that a dynamic menu item has a submenu. The default is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [DefaultValue("Expand {0}")]
        [Description("Alternate text for the image used to indicate that a dynamic menu item has a submenu.")]
        public virtual string DynamicPopOutImageTextFormatString
        {
            get { return this.contextMenu.DynamicPopOutImageTextFormatString; }
            set { this.contextMenu.DynamicPopOutImageTextFormatString = value; }
        }

        /// <summary>
        /// Gets or sets the URL to a custom image that is displayed in a dynamic menu item when the dynamic menu item has a submenu.
        /// </summary>
        /// <value>The URL to an image used to indicate that a dynamic menu item has a submenu. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image used to indicate that a dynamic menu item has a submenu.")]
        [DefaultValue("")]
        public virtual string DynamicPopOutImageUrl
        {
            get { return this.contextMenu.DynamicPopOutImageUrl; }
            set { this.contextMenu.DynamicPopOutImageUrl = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of the dynamic menu item selected by the user.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of the selected dynamic menu item.</value>
        [Category("Style")]
        [Description("Style of the selected dynamic menu item.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual MenuItemStyle DynamicSelectedStyle
        {
            get { return this.contextMenu.DynamicSelectedStyle; }
        }

        /// <summary>
        /// Gets or sets the URL to an image to display at the top of each dynamic menu item to separate it from other menu items. 
        /// </summary>
        /// <value>The URL to a separator image displayed at the top of each dynamic menu item. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to a separator image displayed at the top of each dynamic menu item.")]
        [DefaultValue("")]
        public virtual string DynamicTopSeparatorImageUrl
        {
            get { return this.contextMenu.DynamicTopSeparatorImageUrl; }
            set { this.contextMenu.DynamicTopSeparatorImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the number of pixels to shift a dynamic menu vertically relative to its parent menu item.
        /// </summary>
        /// <value>The number of pixels to shift a dynamic menu vertically relative to its parent menu item. The default is 0.</value>
        [Category("Appearance")]
        [DefaultValue(0)]
        [Description("Number of pixels to shift a dynamic menu vertically relative to its parent menu item.")]
        public virtual int DynamicVerticalOffset
        {
            get { return this.contextMenu.DynamicVerticalOffset; }
            set { this.contextMenu.DynamicVerticalOffset = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the text for menu items should wrap.
        /// </summary>
        /// <value>true to wrap the menu item text; otherwise, false. The default is false.</value>
        [Category("Appearance")]
        [DefaultValue(false)]
        [Description("Whether the text for menu items should wrap.")]
        public virtual bool ItemWrap
        {
            get { return this.contextMenu.ItemWrap; }
            set { this.contextMenu.ItemWrap = value; }
        }

        /// <summary>
        /// Gets or sets the direction in which to render the menu control.
        /// </summary>
        /// <value>One of the Orientation enumeration values. The default is Orientation.Vertical.</value>
        [Category("Layout")]
        [DefaultValue(typeof(Orientation), "Vertical")]
        [Description("Direction in which to render the menu control.")]
        public virtual Orientation Orientation
        {
            get { return this.contextMenu.Orientation; }
            set { this.contextMenu.Orientation = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image displayed in a dynamic menu to indicate that the user can scroll down for additional menu items.
        /// </summary>
        /// <value>The URL to an image displayed in a dynamic menu to indicate that the user can scroll down for additional menu items. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image displayed in a dynamic menu to indicate that the user can scroll down for additional menu items.")]
        [DefaultValue("")]
        public virtual string ScrollDownImageUrl
        {
            get { return this.contextMenu.ScrollDownImageUrl; }
            set { this.contextMenu.ScrollDownImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the alternate text for the image specified in the ScrollDownImageUrl property.
        /// </summary>
        /// <value>The alternate text for the image specified in the ScrollDownImageUrl property. The default is an empty string (""), which indicates that this property is not set.</value>
        [LocalizableAttribute(true)]
        [Category("Appearance")]
        [DefaultValue("Scroll down")]
        [Description("Alternate text for the image specified in the ScrollDownImageUrl property.")]
        public virtual string ScrollDownText
        {
            get { return this.contextMenu.ScrollDownText; }
            set { this.contextMenu.ScrollDownText = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image displayed in a dynamic menu to indicate that the user can scroll up for additional menu items.
        /// </summary>
        /// <value>The URL to an image displayed in a dynamic menu to indicate that the user can scroll up for additional menu items. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image displayed in a dynamic menu to indicate that the user can scroll up for additional menu items.")]
        [DefaultValue("")]
        public virtual string ScrollUpImageUrl
        {
            get { return this.contextMenu.ScrollUpImageUrl; }
            set { this.contextMenu.ScrollUpImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the alternate text for the image specified in the ScrollUpImageUrl property. 
        /// </summary>
        /// <value>The alternate text for the image specified in the ScrollUpImageUrl property. The default is an empty string (""), which indicates that this property is not set.</value>
        [LocalizableAttribute(true)]
        [Category("Appearance")]
        [DefaultValue("Scroll up")]
        [Description("Alternate text for the image specified in the ScrollUpImageUrl property.")]
        public virtual string ScrollUpText
        {
            get { return this.contextMenu.ScrollUpText; }
            set { this.contextMenu.ScrollUpText = value; }
        }

        /// <summary>
        /// Gets or sets the alternate text for a hidden image read by screen readers to provide the ability to skip the list of links.
        /// </summary>
        /// <value>The alternate text of a hidden image read by screen readers to provide the ability to skip the list of links. The default is an empty string (""), which indicates that this property is not set.</value>
        [LocalizableAttribute(true)]
        [Category("Appearance")]
        [DefaultValue("Skip Navigation Links")]
        [Description("Alternate text of a hidden image read by screen readers to provide the ability to skip the list of links.")]
        public virtual string SkipLinkText
        {
            get { return this.contextMenu.SkipLinkText; }
            set { this.contextMenu.SkipLinkText = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image displayed as the separator at the bottom of each static menu item.
        /// </summary>
        /// <value>The URL to an image displayed as the separator at the bottom of each static menu item. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image displayed as the separator at the bottom of each static menu item.")]
        [DefaultValue("")]
        public virtual string StaticBottomSeparatorImageUrl
        {
            get { return this.contextMenu.StaticBottomSeparatorImageUrl; }
            set { this.contextMenu.StaticBottomSeparatorImageUrl = value; }
        }

        /// <summary>
        /// Gets or sets the number of menu levels to display in a static menu.
        /// </summary>
        /// <value>The number of menu levels to display in a static menu. The default is 1.</value>
        [ThemeableAttribute(true)]
        [Category("Behavior")]
        [DefaultValue(1)]
        [Description("Number of menu levels to display in a static menu.")]
        public virtual int StaticDisplayLevels
        {
            get { return this.contextMenu.StaticDisplayLevels; }
            set { this.contextMenu.StaticDisplayLevels = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the built-in image is displayed to indicate that a static menu item has a submenu.
        /// </summary>
        /// <value>true to display the built-in image for static menu items with submenus; otherwise, false. The default is true.</value>
        [Category("Appearance")]
        [DefaultValue(true)]
        [Description("Whether the built-in image is displayed to indicate that a static menu item has a submenu.")]
        public virtual bool StaticEnableDefaultPopOutImage
        {
            get { return this.contextMenu.StaticEnableDefaultPopOutImage; }
            set { this.contextMenu.StaticEnableDefaultPopOutImage = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of a static menu item when the mouse pointer is positioned over it.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of a static menu item when the mouse pointer is positioned over it.</value>
        [Category("Style")]
        [Description("Style of a static menu item when the mouse pointer is positioned over it.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual Style StaticHoverStyle
        {
            get { return this.contextMenu.StaticHoverStyle; }
        }

        /// <summary>
        /// Gets or sets additional text shown with all menu items that are statically displayed.
        /// </summary>
        /// <remarks>The additional text or characters that appear with all menu items.</remarks>
        [Category("Appearance")]
        [Description("Additional text or characters that appear with all menu items.")]
        [DefaultValue("")]
        public virtual string StaticItemFormatString
        {
            get { return this.contextMenu.StaticItemFormatString; }
            set { this.contextMenu.StaticItemFormatString = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of the menu items in a static menu. 
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of the menu items in a static menu.</value>
        [Category("Style")]
        [Description("Style of the menu items in a static menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual MenuItemStyle StaticMenuItemStyle
        {
            get { return this.contextMenu.StaticMenuItemStyle; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of a static menu.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of a static menu.</value>
        [Category("Style")]
        [Description("Style of a static menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual SubMenuStyle StaticMenuStyle
        {
            get { return this.contextMenu.StaticMenuStyle; }
        }

        /// <summary>
        /// Gets or sets the alternate text for the pop-out image used to indicate that a static menu item has a submenu.
        /// </summary>
        /// <value>The alternate text for the pop-out image. The default is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [DefaultValue("Expand {0}")]
        [Description("Alternate text for the pop-out image.")]
        public virtual string StaticPopOutImageTextFormatString
        {
            get { return this.contextMenu.StaticPopOutImageTextFormatString; }
            set { this.contextMenu.StaticPopOutImageTextFormatString = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image displayed to indicate that a static menu item has a submenu.
        /// </summary>
        /// <value>The URL to an image displayed to indicate that a static menu item has a submenu. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image displayed to indicate that a static menu item has a submenu.")]
        [DefaultValue("")]
        public virtual string StaticPopOutImageUrl
        {
            get { return this.contextMenu.StaticPopOutImageUrl; }
            set { this.contextMenu.StaticPopOutImageUrl = value; }
        }

        /// <summary>
        /// Gets a reference to the MenuItemStyle object that allows you to set the appearance of the menu item selected by the user in a static menu.
        /// </summary>
        /// <value>A reference to the MenuItemStyle that represents the style of the selected menu item in a static menu.</value>
        [Category("Style")]
        [Description("Style of the selected menu item in a static menu.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual MenuItemStyle StaticSelectedStyle
        {
            get { return this.contextMenu.StaticSelectedStyle; }
        }

        /// <summary>
        /// Gets or sets the amount of space, in pixels, to indent submenus within a static menu.
        /// </summary>
        /// <value>A Unit that represents the amount of space, in pixels, to indent submenus within a static menu. The default is 0.</value>
        [ThemeableAttribute(true)]
        [Category("Appearance")]
        [DefaultValue(typeof(Unit), "16px")]
        [Description("Amount of space, in pixels, to indent submenus within a static menu.")]
        public virtual Unit StaticSubMenuIndent
        {
            get { return this.contextMenu.StaticSubMenuIndent; }
            set { this.contextMenu.StaticSubMenuIndent = value; }
        }

        /// <summary>
        /// Gets or sets the URL to an image displayed as the separator at the top of each static menu item.
        /// </summary>
        /// <value>The URL to an image displayed as the separator at the top of each static menu item. The default value is an empty string (""), which indicates that this property is not set.</value>
        [Category("Appearance")]
        [Description("URL to an image displayed as the separator at the top of each static menu item.")]
        [DefaultValue("")]
        public virtual string StaticTopSeparatorImageUrl
        {
            get { return this.contextMenu.StaticTopSeparatorImageUrl; }
            set { this.contextMenu.StaticTopSeparatorImageUrl = value; }
        }

        #endregion

        #region Nonbrowsable properties.

        /// <summary>
        /// Gets or sets command hierarchy which defines how <see cref="ICommand"/> objects are grouped and displayed in UI.
        /// </summary>
        /// <remarks>Ignores entries for commands which are not present in the <see cref="Commands"/> collection.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual CommandGroupStyle CommandHierarchy
        {
            get { return this.commandHierarchy; }
            set 
            {
                DisallowPropertyChangeIfMenuItemsCreated("CommandHierarchy");
                this.commandHierarchy = value; 
            }
        }

        /// <summary>
        /// Gets or sets a collection of commands which are to be displayed in the <b>CommandToolBar</b>.
        /// </summary>
        /// <remarks>Commands which are not defined in the <see cref="CommandHierarchy"/> will not be displayed.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList<ICommand> Commands
        {
            get { return this.commands; }
            set 
            {
                DisallowPropertyChangeIfMenuItemsCreated("Commands");
                this.commands = value; 
            }
        }

        /// <summary>
        /// Gets or sets the actor which is using the displayed commands. Nullable.
        /// </summary>
        /// <remarks>When set, the <b>CommandToolBar</b> runs security checks to see which of the commands 
        /// provided in the <see cref="Commands"/> collection are available to the actor.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IActor Actor
        {
            get { return this.actor; }
            set 
            {
                DisallowPropertyChangeIfMenuItemsCreated("Actor");
                this.actor = value; 
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="IMessageFormatter"/> object used to format values in the result grid.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IMessageFormatter Formatter
        {
            get { return this.formatter; }
            set { this.formatter = value; }
        }

        const string showFunctionCall = "Biz_CommandMenu_Show(event, '{0}', '{1}', {2}, '{3}', '{4}', '{5}', {6}); ";

        /// <summary>
        /// Gets the JavaScript code (function call) that displays the menu when in <see cref="CommandMenuType.ContextMenu"/> mode.
        /// </summary>
        /// <remarks>Menu is automatically hidden when next mouse-click event occures.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual string ShowMenuJScript
        {
            get
            {
                EnsureChildControls();
                bool requiresHorizontalSecurity = CheckIfHorizontalSecurityIsRequired();
                string script;
                if (requiresHorizontalSecurity)
                {
                    EnsureMenuItems();
                    int totalItemCount = GetTotalItemCount(this.CommandHierarchy.Items);
                    string bAsyncPostBackOnShow = (this.AsyncPostBackOnShow) ? "true" : "false";
                    script = string.Format(showFunctionCall, this.contextMenu.ClientID, this.btnAsyncCheckCommands.ClientID, totalItemCount, this.txtDisabledItems.ClientID, this.txtHiddenItems.ClientID, this.txtDisabledToolTips.ClientID, bAsyncPostBackOnShow);
                }
                else
                {
                    script = string.Format(showFunctionCall, this.contextMenu.ClientID, "", 0, "", "", "", "false");
                }

                return script;
            }
        }

        private static int GetTotalItemCount(IList<CommandGroupItem> tree)
        {
            int totalCount = 0;
            foreach (CommandGroupItem item in tree)
            {
                totalCount++;
                if (item.Group != null)
                    totalCount += GetTotalItemCount(item.Group.Items);
            }

            return totalCount;
        }

        private bool CheckIfHorizontalSecurityIsRequired()
        {
            bool requiresHorizontalSecurity = false;
            if (this.HorizontalSecurityEnabled && this.Commands != null)
            {
                foreach (ICommand cmd in this.Commands)
                {
                    bool acceptsParams = (cmd.MinParameterSetSize > 0 || cmd.MaxParameterSetSize > 0);
                    if (acceptsParams)
                    {
                        requiresHorizontalSecurity = true;
                        break;
                    }
                }
            }

            return requiresHorizontalSecurity;
        }

        /// <summary>
        /// Gets value indicating whether the AJAX dependent features of the <b>CommandToolBar</b> are enabled.
        /// </summary>
        /// <value><b>true</b> if the <b>Page</b> to which the control belongs to contains a <b>ScriptManager</b> control; <b>false</b> otherwise.</value>
        /// <remarks>The <b>CommandToolBar</b> will automatically disable all features which use AJAX extensions, 
        /// such as horizontal security enforcing, if the <b>ScriptManager</b> control is not present.</remarks>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool AjaxEnabled
        {
            get { return this.ajaxEnabled; }
        }

        const string enableAllItemsFunctionCall = "Biz_CommandMenu_EnableAll('{0}', {1}); ";

        /// <summary>
        /// Gets the JavaScript code (function call) that enables all commands available to actor.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual string EnableAllCommandsScript
        {
            get
            {
                EnsureChildControls();
                bool requiresHorizontalSecurity = CheckIfHorizontalSecurityIsRequired();
                string script;
                if (requiresHorizontalSecurity)
                {
                    EnsureMenuItems();
                    int totalItemCount = GetTotalItemCount(this.CommandHierarchy.Items);
                    script = string.Format(enableAllItemsFunctionCall, this.contextMenu.ClientID, totalItemCount);
                }
                else
                {
                    script = string.Format(enableAllItemsFunctionCall, this.contextMenu.ClientID, 0);
                }

                return script;
            }
        }

        #endregion

        #region CommandMenu properties.

        /// <summary>
        /// Gets or sets the type of menu. Default is <see cref="CommandMenuType.MenuStrip"/>.
        /// </summary>
        [DefaultValue(typeof(CommandMenu.CommandMenuType), "MenuStrip")]
        [Category("Layout")]
        [Description("Menu type.")]
        public virtual CommandMenuType MenuType
        {
            get { return this.menuType; }
            set { this.menuType = value; }
        }

        bool autoGenerateCommandHierarchy = true;

        /// <summary>
        /// Gets or sets whether the <see cref="CommandHierarchy"/> items are automatically generated in
        /// <b>Render</b> stage based on the command information stored in <see cref="Commands"/>.
        /// </summary>
        /// <value><b>true</b> if command items are automatically created. Default is <b>true</b>.</value>
        /// <remarks>Automatic generation in rendering stage occurs only if the <see cref="CommandHierarchy"/> 
        /// is empty. Only items for commands that match <see cref="MinParameterSetSize"/> and
        /// <see cref="MaxParameterSetSize"/> criteria are generated.</remarks>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Indicates whether CommandHierarchy items are automatically generated.")]
        public virtual bool AutoGenerateCommandHierarchy
        {
            get { return this.autoGenerateCommandHierarchy; }
            set { this.autoGenerateCommandHierarchy = value; }
        }

        int minParameterSetSize = -1;

        /// <summary>
        /// Gets or sets the minimum parameter-set size of the commands which are to be displayed in the menu.
        /// </summary>
        /// <value>Positive number which specifies the minimum parameter-set size or a negative value if no
        /// constraints should be applied. Default is <b>-1</b> indicating that all defined commands
        /// are to be displayed.</value>
        [Category("Behavior")]
        [DefaultValue(-1)]
        [Description("Minimum parameter-set size of the commands which are to be displayed in the menu.")]
        public virtual int MinParameterSetSize
        {
            get { return this.minParameterSetSize; }
            set { this.minParameterSetSize = value; }
        }

        int maxParameterSetSize = -1;

        /// <summary>
        /// Gets or sets the maximum parameter-set size of the commands which are to be displayed in the menu.
        /// </summary>
        /// <value>Positive number which specifies the maximum parameter-set size or a negative value if no
        /// constraints should be applied. Default is <b>-1</b> indicating that all defined commands
        /// are to be displayed.</value>
        [Category("Behavior")]
        [DefaultValue(-1)]
        [Description("Maximum parameter-set size of the commands which are to be displayed in the menu.")]
        public virtual int MaxParameterSetSize
        {
            get { return this.maxParameterSetSize; }
            set { this.maxParameterSetSize = value; }
        }

        string commandHierarchySrc;

        /// <summary>
        /// Gets or sets the URL of the XML file which is read by <see cref="CommandHierarchy"/>.
        /// </summary>
        [Category("Behavior")]
        [Editor(typeof(XmlUrlEditor), typeof(UITypeEditor))]
        [Description("URL of the XML file which is read by CommandHierarchy.")]
        [DefaultValue(null)]
        public virtual string CommandHierarchySrc
        {
            get { return this.commandHierarchySrc; }
            set { this.commandHierarchySrc = value; }
        }        

        /// <summary>
        /// Gets or sets whether the reason why an item is disabled is displayed in a tooltip.
        /// </summary>
        /// <value><b>true</b> if business rules which prevent the execution of the command are displayed; 
        /// <b>false</b> otherwise. Default is <b>true</b>.</value>
        /// <remarks>The reason is obtained by executing the <see cref="ICommand.GrantExecute"/> method.</remarks>
        [Category("Appearance")]
        [DefaultValue(true)]
        [Description("Whether the reason why an item is disabled is displayed in a tooltip.")]
        public virtual bool DisplayReasonForDisabledItems
        {
            get { return this.displayReasonForDisabledItems; }
            set { this.displayReasonForDisabledItems = value; }
        }

        /// <summary>
        /// Gets or sets whether the <b>CommandMenu</b> will automatically enforce horizontal security, 
        /// ie. check which commands can be executed.
        /// </summary>
        /// <value><b>true</b> if the <b>CommandMenu</b> should disable or hide the commands which cannot
        /// be executed; <b>false</b> otherwise. Default is <b>true</b>.</value>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Whether the CommandMenu will automatically enforce horizontal security.")]
        public virtual bool HorizontalSecurityEnabled
        {
            get { return this.horizontalSecurityEnabled; }
            set { this.horizontalSecurityEnabled = value; }
        }

        /// <summary>
        /// Gets or sets whether the <b>CommandMenu</b> will automatically initiate an asynchronous postback when it's displayed.
        /// </summary>
        /// <value><b>true</b> if the <b>CommandMenu</b> should initiate an asynchronous postback when it's 
        /// displayed; <b>false</b> otherwise. Default is <b>false</b>.</value>
        /// <remarks><para>The <b>CommandMenu</b> enforces horizontal security during all asynchronous postbacks 
        /// if the <see cref="HorizontalSecurityEnabled"/> property is set to true.</para>
        /// <para>If another control on the same page is used to initiate asynchronous postbacks then set this 
        /// property to <b>false</b>. Otherwise, duplicate postbacks might occure reducing overall application 
        /// performance. Set to <b>true</b> only if the <b>CommandMenu</b> is the only control on the page which 
        /// is capable of initating asynchronous postbacks.</para>
        /// <para>Has no effect if the <see cref="MenuType"/> property is not set to 
        /// <see cref="CommandMenuType.ContextMenu"/> value.</para></remarks>
        [Category("Behavior")]
        [DefaultValue(false)]
        [Description("Whether the CommandMenu will automatically initiate an asynchronous postback when it's displayed.")]
        public virtual bool AsyncPostBackOnShow
        {
            get { return this.asyncPostBackOnShow; }
            set { this.asyncPostBackOnShow = value; }
        }

        #endregion        
    }

    /// <summary>
    /// Performs design-time rendering of a <see cref="CommandMenu"/> control.
    /// </summary>
    internal class CommandMenuDesigner : ControlDesigner
    {
        #region GetDesignTimeHtml.

        /// <summary>
        /// Retrieves the HTML markup that is used to represent the control at design time.
        /// </summary>
        /// <returns>The HTML markup used to represent the control at design time.</returns>
        public override string GetDesignTimeHtml()
        {
            CommandMenu menu = new CommandMenu();
            ReflectionUtil.CopyPublicProperties(this.Component, menu);

            menu.Visible = true;
            menu.Commands = new List<ICommand>();
            menu.Commands.Add(new JsCommand("cmd1", "Do stuff", "alert('Do stuff');"));
            menu.Commands.Add(new JsCommand("cmd2", "Do something else", "alert('Do something else');"));
            menu.CommandHierarchy = new CommandGroupStyle();
            menu.CommandHierarchy.Items.Add(new CommandGroupItem(new CommandStyle("cmd1", null, null, CommandStyle.DisableStyle.Automatic, CommandStyle.DisableStyle.Automatic)));
            menu.CommandHierarchy.Items.Add(new CommandGroupItem(new CommandStyle("cmd2", null, null, CommandStyle.DisableStyle.Automatic, CommandStyle.DisableStyle.Automatic)));
            menu.MinParameterSetSize = -1;
            menu.MaxParameterSetSize = -1;
            string designTimeHtml = menu.GetDesignTimeHtml();

            return designTimeHtml;
        }

        #endregion
    }
}
